package xyz.renyumeng.xkm.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xyz.renyumeng.xkm.client.shop.ProductInfoFeignClient;
import xyz.renyumeng.xkm.client.user.UserInfoFeignClient;
import xyz.renyumeng.xkm.dto.order.UserOrderDto;
import xyz.renyumeng.xkm.dto.order.UserOrderPage;
import xyz.renyumeng.xkm.exception.XkmException;
import xyz.renyumeng.xkm.model.order.OrderInfo;
import xyz.renyumeng.xkm.model.shop.ProductInfo;
import xyz.renyumeng.xkm.model.user.UserInfo;
import xyz.renyumeng.xkm.order.mapper.UserOrderMapper;
import xyz.renyumeng.xkm.order.service.UserOrderService;
import xyz.renyumeng.xkm.redis.CacheClient;
import xyz.renyumeng.xkm.redis.ExpireTime;
import xyz.renyumeng.xkm.redis.RedisData;
import xyz.renyumeng.xkm.result.Result;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import static xyz.renyumeng.xkm.redis.RedisConstants.*;

/**
 * @author renyumeng
 * @date Created in 21:50 2023/6/30
 */

@Service
@Slf4j
@Transactional
public class UserOrderServiceImpl extends ServiceImpl<UserOrderMapper, OrderInfo> implements UserOrderService {


    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private UserInfoFeignClient userInfoFeignClient;

    @Resource
    ProductInfoFeignClient productInfoFeignClient;

    @Resource
    CacheClient cacheClient;

    @Override
    public Result<List<UserOrderDto>> getUserOrderInfos(UserOrderPage userOrderPage) {
        Long userId = userOrderPage.getUserId();
        Long page = userOrderPage.getPage();
        Long count = userOrderPage.getCount();
        String filter = userOrderPage.getFilter();
        String key = ORDER_INFO_KEY + userId;
        String field = "expireTime";

        //TODO 逻辑过期 判断expireTime是否过期，过期返回旧数据，开启新线程重建缓存
        if (BooleanUtil.isFalse(stringRedisTemplate.opsForHash().hasKey(key, field))) {
            return Result.ok(null);
        }

        //不存在直接返回null
        if (BooleanUtil.isFalse(stringRedisTemplate.hasKey(key))) {
            return Result.ok(null);
        }
        // 转成localDateTime对象
        Object expireTimeJson = stringRedisTemplate.opsForHash().get(key, field);
        String expireTimeStr = StrUtil.toString(expireTimeJson);
        LocalDateTime expireTime = transferExpireTimeStrToLocalDateTime(expireTimeStr);
        // 判断是否过期
        if (expireTime.isBefore(LocalDateTime.now())) {
            // 过期重新构建缓存
            String lockKey = LOCK_ORDER_INFO_KEY + userId;
            boolean isLock = cacheClient.tryLock(lockKey);
            if (isLock) {
                // TODO 成功，开启独立线程，实现缓存重建
                CACHE_REBUILD_EXECUTOR.submit(() -> {
                    try {
                        this.rebuildUserOrdersCache(userId, key);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    } finally {
                        // 释放锁
                        cacheClient.unlock(lockKey);
                    }
                });
            }
        }
        List<UserOrderDto> userOrderDtoList = pageFromHashMap(key, count, page, filter);
        return Result.ok(userOrderDtoList).message("查询成功!");
    }

    @Override
    public Result<UserOrderDto> getUserOrderInfo(Long userId, Long orderId) {
        String key = ORDER_INFO_KEY + userId;
        String orderIdField = StrUtil.toString(orderId);
        // 1.缓存是否存在，不存在空值返回
        Object userOrderObject = stringRedisTemplate.opsForHash().get(key, orderIdField);
        if (BooleanUtil.isTrue(ObjectUtil.isEmpty(userOrderObject))) {
            return Result.ok(null);
        }
        String userOrderStr = JSONUtil.toJsonStr(userOrderObject);
        // 2.判断逻辑过期，过期重建，未过期直接返回
        OrderInfo userOrderInfo = JSONUtil.toBean(userOrderStr, OrderInfo.class);
        UserOrderDto userOrderDto = new UserOrderDto();
        BeanUtil.copyProperties(userOrderInfo, userOrderDto);
        return Result.ok(userOrderDto);
    }

    @Override
    public Result<Object> deleteOrder(Long orderId) {
        OrderInfo orderInfo = baseMapper.selectById(orderId);
        boolean isOk = this.removeById(orderId);
        if (!isOk) {
            throw new XkmException(201, "删除的订单不存在");
        }
        // 读写分离，开启一个新线程 redis删
        CACHE_REBUILD_EXECUTOR.submit(() -> {

            Long purchaseUserId = orderInfo.getPurchaseUserId();
            String key = ORDER_INFO_KEY + purchaseUserId;
            String field = StrUtil.toString(orderId);
            Long delete = stringRedisTemplate.opsForHash().delete(key, field);
            log.info("删除成功");

        });

        return Result.ok(null).message("删除成功");
    }

    @Override
    public Result<List<OrderInfo>> getUnpaidOrder(Long userId) {
        String queueOrderKey = QUEUE_ORDER_KEY + userId + ":*";
        Set<String> keys = stringRedisTemplate.keys(queueOrderKey);
        if (CollUtil.isEmpty(keys)) {
            List<OrderInfo> empty = ListUtil.empty();
            return Result.ok(empty).message("没有未付款的订单!");
        }
        List<OrderInfo> orderInfoList = keys.stream().map(key -> {
            String orderJson = stringRedisTemplate.opsForValue().get(key);
            return JSONUtil.toBean(orderJson, OrderInfo.class);
        }).collect(Collectors.toList());
        return Result.ok(orderInfoList).message("查询成功!");
    }

    private LocalDateTime transferExpireTimeStrToLocalDateTime(String expireTimeStr) {
        ExpireTime expire = JSONUtil.toBean(expireTimeStr, ExpireTime.class);
        return expire.getExpireTime();
    }

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = new ThreadPoolExecutor(
            10,
            15,
            3,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(3),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.DiscardOldestPolicy());

    private void rebuildUserOrdersCache(Long userId, String key) {
        LambdaQueryWrapper<OrderInfo> orderInfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
        orderInfoLambdaQueryWrapper.eq(OrderInfo::getPurchaseUserId, userId);
        List<OrderInfo> orderInfoList = this.list(orderInfoLambdaQueryWrapper);

        if (CollUtil.isEmpty(orderInfoList)) {
            throw new XkmException(201, "用户不存在!");
        }
        orderInfoList.forEach(orderInfo -> {
            putUserOrderInfo(key, orderInfo);
        });
        // 写入逻辑过期
        RedisData redisData2Expire = new RedisData();
        redisData2Expire.setData(null);
        redisData2Expire.setExpireTime(LocalDateTime.now().plusDays(TimeUnit.DAYS.toDays(ORDER_INFO_TTL)));
        stringRedisTemplate.opsForHash().put(key, "expireTime", JSONUtil.toJsonStr(redisData2Expire));
    }

    private void putUserOrderInfo(String key, OrderInfo orderInfo) {
        UserInfo ownerUserInfo = userInfoFeignClient.innerGetUserByIdWithAddressInfo(orderInfo.getOwnerId());
        UserInfo purchaseUserInfo = userInfoFeignClient.innerGetUserByIdWithAddressInfo(orderInfo.getPurchaseUserId());
        ProductInfo productInfo = productInfoFeignClient.innerGetProductInfoAndOwnerUserInfoById(orderInfo.getProductId());

        orderInfo.getParam().put("productDesc", productInfo.getDesc());
        orderInfo.getParam().put("ownerUserName", ownerUserInfo.getUsername());
        orderInfo.getParam().put("purchaseUserName", purchaseUserInfo.getUsername());
        // 添加地址信息
        orderInfo.getParam().put("purchaseUserAddressInfo", purchaseUserInfo.getParam().get("addressInfo"));
        orderInfo.getParam().put("productName", productInfo.getProductName());
        orderInfo.getParam().put("productType", productInfo.getParam().get("type"));

        orderInfo.getParam().put("createTime", orderInfo.getCreateTime());

        stringRedisTemplate.opsForHash().put(key, StrUtil.toString(orderInfo.getId()), JSONUtil.toJsonStr(orderInfo));
    }

    private List<UserOrderDto> pageFromHashMap(String key, Long pageSize, Long pageNum, String filter) {


        List<UserOrderDto> results = new ArrayList<>();
        long startCursor = (pageNum - 1) * pageSize;
        ScanOptions options = ScanOptions.scanOptions().count(pageSize).build();
        Cursor<Map.Entry<Object, Object>> cursor = stringRedisTemplate.opsForHash().scan(key, options);
        int i = 0;
        while (cursor.hasNext()) {

            Map.Entry<Object, Object> entry = cursor.next();
            String jsonValue = entry.getValue().toString();
            String expireKey = entry.getKey().toString();
            if (StrUtil.equals(expireKey, "expireTime")) {
                log.info("到expireTime了");
                continue;
            }
            UserOrderDto userOrderDto = JSONUtil.toBean(jsonValue, UserOrderDto.class);

            Object productNameObj = userOrderDto.getParam().get("productName");
            String productName = Convert.toStr(productNameObj);
            boolean contains = true;
            if (StrUtil.isNotBlank(filter)) {
                contains = productName.contains(filter);
            }
            if (contains) {
                if (i >= startCursor && i < startCursor + pageSize) { // 判断是否在当前页范围内
                    results.add(userOrderDto);
                    i++;
                }
            }
        }
        return results;
    }


}
